#include "IEC101_encoder.h"
#include "IEC_asduEncoder.h"
#include "IEC101_def.h"
#include <memory>

using namespace XIEC101;

xuint8 CIEC101Encoder::constLengthFrame(xuint8* dest, xuint8 ctrlField, xuint16 slave_id, const Settings *settings)
{
    xuint8 pos = 0;
    dest[pos++] = START_CHAR_CONST;
    dest[pos++] = ctrlField;
    dest[pos++] = slave_id & 0xFF;
    if (settings->link_addr_len > 1)
        dest[pos++] = slave_id>>8;
    dest[pos] = getCheckSum(dest+1, pos-1);
    pos++;
    dest[pos++] = END_CHAR;
    return pos;
}

xuint8 CIEC101Encoder::requestLinkStatus(xuint8 *dest, xuint16 slave_id, const Settings *settings)
{
    C_CtrlField ctrl;
    ctrl.bits.FC = FC_REQUEST_LINK_STATUS;
    return constLengthFrame(dest, ctrl.data, slave_id, settings);
}

xuint8 CIEC101Encoder::resetLink(xuint8 *dest, xuint16 slave_id, const Settings *settings)
{
    C_CtrlField ctrl;
    ctrl.bits.FC = FC_REMOTE_LINK_RESET;
    return constLengthFrame(dest, ctrl.data, slave_id, settings);
}

xuint8 CIEC101Encoder::requestPrimaryData(xuint8 *dest, bool fcb, xuint16 slave_id, const Settings *settings)
{
    C_CtrlField ctrl;
    ctrl.bits.FC = FC_REQUEST_PRIMARY_DATA;
    ctrl.bits.FCV = true;
    ctrl.bits.FCB = fcb;
    return constLengthFrame(dest, ctrl.data, slave_id, settings);
}

xuint8 CIEC101Encoder::requestSecondaryData(xuint8 *dest, bool fcb, xuint16 slave_id, const Settings *settings)
{
    C_CtrlField ctrl;
    ctrl.bits.FC = FC_REQUEST_SECONDARY_DATA;
    ctrl.bits.FCV = true;
    ctrl.bits.FCB = fcb;
    return constLengthFrame(dest, ctrl.data, slave_id, settings);
}

xuint8* CIEC101Encoder::asduPtr(xuint8* dest, const Settings *settings)
{
    if (settings->link_addr_len == 1)
        return dest+6;
    else
        return dest+7;
}

xuint8 CIEC101Encoder::variableLengthFrame(xuint8* dest, xuint8 ctrlField, xuint8 typeId, VSQ vsq, xuint8 cot, CVectInfoObject& infoObjects, xuint16 slave_id, const Settings *settings)
{
    DataUnitTag dataUnit;
    dataUnit.typeId = typeId;
    dataUnit.cot16 = cot;
    dataUnit.vsq.d = vsq.d;
    dataUnit.ca16 = slave_id;
    xuint8  asduLen = CAsduEncoder::encode(asduPtr(dest, settings), dataUnit, infoObjects, settings->iec_settings);
    dest[0] = dest[3] = START_CHAR_VARIABLE;
    dest[1] = dest[2] = asduLen+1+settings->link_addr_len;
    dest[4] = ctrlField;
    xuint8 pos = 5;
    dest[pos++] = slave_id & 0xFF;
    if (settings->link_addr_len > 1)
        dest[pos++] = slave_id>>8;
    pos += asduLen;
    dest[pos++] = getCheckSum(dest+4, dest[1]);
    dest[pos++] = END_CHAR;
    return pos;
}

xuint8 CIEC101Encoder::variableLengthFrame(xuint8* dest, xuint8 ctrlField, xuint8 typeId, VSQ vsq, xuint8 cot, CInfoObject& infoObject, xuint16 slave_id, const Settings *settings)
{
    DataUnitTag dataUnit;
    dataUnit.typeId = typeId;
    dataUnit.cot16 = cot;
    dataUnit.vsq.d = vsq.d;
    dataUnit.ca16 = slave_id;
    xuint8  asduLen = CAsduEncoder::encode(asduPtr(dest, settings), dataUnit, infoObject, settings->iec_settings);
    dest[0] = dest[3] = START_CHAR_VARIABLE;
    dest[1] = dest[2] = asduLen+1+settings->link_addr_len;
    dest[4] = ctrlField;
    xuint8 pos = 5;
    dest[pos++] = slave_id & 0xFF;
    if (settings->link_addr_len > 1)
        dest[pos++] = slave_id>>8;
    pos += asduLen;
    dest[pos++] = getCheckSum(dest+4, dest[1]);
    dest[pos++] = END_CHAR;
    return pos;
}

xuint8 CIEC101Encoder::gen(xuint8 *dest, bool fcb, xuint8 qoi, xuint16 slave_id, const Settings *settings)
{
    C_CtrlField ctrl;
    ctrl.bits.FC = FC_TRANSFER_DATA_WITH_ACK;
    ctrl.bits.FCV = true;
    ctrl.bits.FCB = fcb;
    CInfoObject100 obj(qoi);
    return variableLengthFrame(dest, ctrl.data, 100, VSQ(1), COT_ACT, obj, slave_id, settings);
}
